use sha2::{Digest, Sha256};

use crate::prelude::*;

/// Encodes the into from a new vector.
pub fn encode_to_vec<T: Encode>(v: &T) -> Result<Vec<u8>, CodecError> {
    let mut buf = Vec::new();
    encode_into(v, &mut buf)?;
    Ok(buf)
}

/// Encodes the value into a provided vector.
pub fn encode_into<T: Encode>(v: &T, into: &mut Vec<u8>) -> Result<(), CodecError> {
    v.encode(into)
}

/// Decodes a value of the type from the buf, asserting no bytes are leftover.
pub fn decode<T: Decode>(buf: &[u8]) -> Result<T, CodecError> {
    let mut dec = BufDecoder::new(buf);
    let v = T::decode(&mut dec)?;
    if !dec.finished() {
        return Err(CodecError::DanglingInput(dec.bytes_left()));
    }
    Ok(v)
}

/// Decodes a value of the type from the buf, without asserting there's no bytes
/// leftover.  Returns the number of bytes left over after decoding.
pub fn decode_inexact<T: Decode>(buf: &[u8]) -> Result<(T, usize), CodecError> {
    let mut dec = BufDecoder::new(buf);
    let v = T::decode(&mut dec)?;
    Ok((v, dec.bytes_left()))
}

/// Decodes an instance of a type that supports zero-copy deserialization.
pub fn decode_borrowed<'de, T: DecodeRef<'de>>(buf: &'de [u8]) -> Result<T, CodecError> {
    let mut dec = BufDecoder::new(buf);
    let v = T::decode_ref(&mut dec)?;
    Ok(v)
}

/// Combines the behavior of both `decode_inexact` and `decode_borrowed`.
pub fn decode_borrowed_inexact<'de, T: DecodeRef<'de>>(
    buf: &'de [u8],
) -> Result<(T, usize), CodecError> {
    let mut dec = BufDecoder::new(buf);
    let v = T::decode_ref(&mut dec)?;
    Ok((v, dec.bytes_left()))
}

struct DigestEncoder<D: Digest> {
    digest: D,
}

impl<D: Digest> Encoder for DigestEncoder<D> {
    fn push_next(&mut self, buf: &[u8]) -> Result<(), CodecError> {
        self.digest.update(buf);
        Ok(())
    }
}

/// Computes the SHA256 hash of an encodable type, *without* serializing the
/// entire type into a buffer in memory.
pub fn compute_sha256<T: Encode>(v: &T) -> Result<[u8; 32], CodecError> {
    let mut denc = DigestEncoder {
        digest: Sha256::new(),
    };
    v.encode(&mut denc)?;
    let h: [u8; 32] = denc.digest.finalize().into();
    Ok(h)
}

/// Computes the SHA256 of a raw buffer, like `compute_sha256`.
pub fn compute_sha256_raw(buf: &[u8]) -> [u8; 32] {
    Sha256::digest(buf).into()
}
